{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Qwen1.5 implemented from scratch with kvcache\n",
    "in this file, i implemented qwen1.5-4B from scratch, one tensor and matrix multiplication at a time.\n",
    "<br><br>\n",
    "the weights is huggingface format, suffix with safetensors.\n",
    "<br><br>\n",
    "use \"Genius is one percent inspiration and\" as prompt and generate result \"ninety-nine percent perspiration.\" in an auto-regressive manner.\n",
    "<br><br>\n",
    "Cache the vector of k and v during the prefill phase. and load it in decoder phase.\n",
    "\n",
    "从头开始实现Qwen1.5，支持huggingface格式，以safetensors为后缀的权重，并支持kvcache，进行自回归推理。\n",
    "\n",
    "以 \"Genius is one percent inspiration and\" 作为输入的prompt，先进行prefill的并行运算，生成并缓存 k 和 v，然后进行自回归生成，将前一次输出作为下一次输入，同时加载缓存的kvcache，进行推理，逐个token生成\"ninety-nine percent perspiration\"。\n",
    "\n",
    "前面的推理和原过程是一样的，只是在迭代layer生成第一个token时，缓存了k和v。生成第一个token之后，将其作为输入，是新增部分。\n",
    "\n",
    "这里以Qwen1.5-4B为例，其他不同大小的模型需要修改个别参数适配。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## tokenizer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 191,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'hello world!'"
      ]
     },
     "execution_count": 191,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from tokenizers import Tokenizer as TokenizerFast\n",
    "import os\n",
    "\n",
    "model_path = \"../qwen/Qwen1_5-4B\"\n",
    "\n",
    "tokenizer = TokenizerFast.from_file(os.path.join(model_path, 'tokenizer.json'))\n",
    "tokenizer.decode(tokenizer.encode(\"hello world!\").ids)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## reading the model file\n",
    "normally, reading this depends on how the model classes are written and the variable names inside them.\n",
    "<br>\n",
    "but since we are implementing qwen1.5 from scratch we will read the file one tensor at a time."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 192,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "model.embed_tokens.weight torch.Size([151936, 2560])\n",
      "model.layers.0.input_layernorm.weight torch.Size([2560])\n",
      "model.layers.0.mlp.down_proj.weight torch.Size([2560, 6912])\n",
      "model.layers.0.mlp.gate_proj.weight torch.Size([6912, 2560])\n",
      "model.layers.0.mlp.up_proj.weight torch.Size([6912, 2560])\n",
      "model.layers.0.post_attention_layernorm.weight torch.Size([2560])\n",
      "model.layers.0.self_attn.k_proj.bias torch.Size([2560])\n",
      "model.layers.0.self_attn.k_proj.weight torch.Size([2560, 2560])\n",
      "model.layers.0.self_attn.o_proj.weight torch.Size([2560, 2560])\n",
      "model.layers.0.self_attn.q_proj.bias torch.Size([2560])\n",
      "model.layers.0.self_attn.q_proj.weight torch.Size([2560, 2560])\n",
      "model.layers.0.self_attn.v_proj.bias torch.Size([2560])\n",
      "model.layers.0.self_attn.v_proj.weight torch.Size([2560, 2560])\n",
      "model.layers.1.input_layernorm.weight torch.Size([2560])\n",
      "model.layers.1.mlp.down_proj.weight torch.Size([2560, 6912])\n",
      "model.layers.1.mlp.gate_proj.weight torch.Size([6912, 2560])\n",
      "model.layers.1.mlp.up_proj.weight torch.Size([6912, 2560])\n",
      "model.layers.1.post_attention_layernorm.weight torch.Size([2560])\n"
     ]
    }
   ],
   "source": [
    "from safetensors import safe_open\n",
    "model = {}\n",
    "for i in range(1,3):  # 遍历加载权重文件，不同大小的模型需要针对性修改\n",
    "    with safe_open(os.path.join(model_path, f\"model-0000{i}-of-00002.safetensors\"), framework=\"pt\", device=\"cpu\") as f:\n",
    "        count = 1\n",
    "        for k in f.keys():\n",
    "            model[k] = f.get_tensor(k)\n",
    "            count += 1\n",
    "            if count < 20 and i == 1:   # 仅显示部分权重及其shape，作为示例\n",
    "                print(k, model[k].shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 193,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'architectures': ['Qwen2ForCausalLM'],\n",
       " 'attention_dropout': 0.0,\n",
       " 'bos_token_id': 151643,\n",
       " 'eos_token_id': 151643,\n",
       " 'hidden_act': 'silu',\n",
       " 'hidden_size': 2560,\n",
       " 'initializer_range': 0.02,\n",
       " 'intermediate_size': 6912,\n",
       " 'max_position_embeddings': 32768,\n",
       " 'max_window_layers': 21,\n",
       " 'model_type': 'qwen2',\n",
       " 'num_attention_heads': 20,\n",
       " 'num_hidden_layers': 40,\n",
       " 'num_key_value_heads': 20,\n",
       " 'rms_norm_eps': 1e-06,\n",
       " 'rope_theta': 5000000.0,\n",
       " 'sliding_window': 32768,\n",
       " 'tie_word_embeddings': False,\n",
       " 'torch_dtype': 'bfloat16',\n",
       " 'transformers_version': '4.37.0',\n",
       " 'use_cache': True,\n",
       " 'use_sliding_window': False,\n",
       " 'vocab_size': 151936}"
      ]
     },
     "execution_count": 193,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import json\n",
    "with open(os.path.join(model_path, 'config.json'), \"r\") as f:   \n",
    "    config = json.load(f)\n",
    "config"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## we use this config to infer details about the model like\n",
    "1. the model has 40 transformer layers\n",
    "2. each multi-head attention block has 20 heads\n",
    "3. the vocab size and so on"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 194,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "dim = config[\"hidden_size\"]\n",
    "n_layers = config[\"num_hidden_layers\"]\n",
    "n_heads = config[\"num_attention_heads\"]\n",
    "n_kv_heads = config[\"num_key_value_heads\"]\n",
    "vocab_size = config[\"vocab_size\"]\n",
    "# multiple_of = config[\"multiple_of\"]\n",
    "ffn_dim_multiplier = config[\"intermediate_size\"]\n",
    "norm_eps = config[\"rms_norm_eps\"]\n",
    "rope_theta = torch.tensor(config[\"rope_theta\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## converting text to tokens\n",
    "here we use tiktoken (i think an openai library) as the tokenizer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 195,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[9967, 9156, 374, 825, 3266, 19760, 323]\n",
      "['Gen', 'ius', ' is', ' one', ' percent', ' inspiration', ' and']\n"
     ]
    }
   ],
   "source": [
    "# prompt = \"the answer to the ultimate question of life, the universe, and everything is \"\n",
    "# prompt = \"Failure is the mother of\"\n",
    "prompt = \"Genius is one percent inspiration and\"   # generate “ninety-nine percent perspiration.”\n",
    "tokens = tokenizer.encode(prompt).ids\n",
    "print(tokens)\n",
    "tokens = torch.tensor(tokens)\n",
    "prompt_split_as_tokens = [tokenizer.decode([token.item()]) for token in tokens]\n",
    "print(prompt_split_as_tokens)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## converting tokens to their embedding\n",
    "IM SORRY but this is the only part of the codebase where i use an inbuilt neural network module\n",
    "<br>\n",
    "anyway, so our [7x1] tokens are now [2560], i.e. 7 embeddings (one for each token) of length 2560\n",
    "<br>\n",
    "<br>\n",
    "note: keep track of the shapes, it makes it much easier to understand everything"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 196,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 2560])"
      ]
     },
     "execution_count": 196,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "embedding_layer = torch.nn.Embedding(vocab_size, dim)\n",
    "embedding_layer.weight.data.copy_(model[\"model.embed_tokens.weight\"])\n",
    "token_embeddings_unnormalized = embedding_layer(tokens).to(torch.bfloat16)\n",
    "token_embeddings_unnormalized.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## we then normalize the embedding using rms normalization\n",
    "please, note after this step the shapes dont change, the values are just normalized\n",
    "<br>\n",
    "things to keep in mind, we need a norm_eps (from config) because we dont want to accidently set rms to 0 and divide by 0\n",
    "<br>\n",
    "here is the formula:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 197,
   "metadata": {},
   "outputs": [],
   "source": [
    "# def rms_norm(tensor, norm_weights):\n",
    "#     rms = (tensor.pow(2).mean(-1, keepdim=True) + norm_eps)**0.5\n",
    "#     return tensor * (norm_weights / rms)\n",
    "def rms_norm(tensor, norm_weights):\n",
    "    return (tensor * torch.rsqrt(tensor.pow(2).mean(-1, keepdim=True) + norm_eps)) * norm_weights"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# building the first layer of the transformer\n",
    "\n",
    "### normalization\n",
    "you will see me accessing layer.0 from the model dict (this is the first layer)\n",
    "<br>\n",
    "anyway, so after normalizing our shapes are still [7x2560] same as embedding but normalized "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 250,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.bfloat16\n"
     ]
    }
   ],
   "source": [
    "token_embeddings = rms_norm(token_embeddings_unnormalized, model[\"model.layers.0.input_layernorm.weight\"])\n",
    "token_embeddings.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### attention implemented from scratch\n",
    "let's load the attention heads of the first layer of the transformer\n",
    "\n",
    "&gt; when we load the query, key, value and output vectors from the model we notice the shapes to be [2560x2560], [2560x2560], [2560x2560], [2560x2560]\n",
    "<br>\n",
    "&gt; at first glance this is weird because ideally we want each q,k,v and o for each head individually\n",
    "<br>\n",
    "&gt; the authors of the code bundled them togeather because its easy it helps parallize attention head multiplication.\n",
    "<br>\n",
    "&gt; im going to unwrap everything... "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 199,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([2560, 2560]) torch.Size([2560]) torch.Size([2560, 2560]) torch.Size([2560]) torch.Size([2560, 2560]) torch.Size([2560]) torch.Size([2560, 2560])\n"
     ]
    }
   ],
   "source": [
    "print(\n",
    "    model[\"model.layers.0.self_attn.q_proj.weight\"].shape,\n",
    "    model[\"model.layers.0.self_attn.q_proj.bias\"].shape,\n",
    "    model[\"model.layers.0.self_attn.k_proj.weight\"].shape,\n",
    "    model[\"model.layers.0.self_attn.k_proj.bias\"].shape,\n",
    "    model[\"model.layers.0.self_attn.v_proj.weight\"].shape,\n",
    "    model[\"model.layers.0.self_attn.v_proj.bias\"].shape,\n",
    "    model[\"model.layers.0.self_attn.o_proj.weight\"].shape\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### unwrapping query\n",
    "in the next section we will unwrap the queries from multiple attention heads, the resulting shape is [20x128x2560]\n",
    "<br><br>\n",
    "here, 20 is the number of attention heads in qwen1.5-4B, 128 is the size of the query vector and 2560 is the size of the token embedding"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 200,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([20, 128, 2560])\n",
      "torch.Size([20, 128])\n"
     ]
    }
   ],
   "source": [
    "q_layer0 = model[\"model.layers.0.self_attn.q_proj.weight\"]\n",
    "head_dim = q_layer0.shape[0] // n_heads\n",
    "q_layer0 = q_layer0.view(n_heads, 2, head_dim//2, dim).permute(0,2,1,3).reshape(n_heads, head_dim, dim)\n",
    "print(q_layer0.shape)\n",
    "\n",
    "q_layer0_bias = model[\"model.layers.0.self_attn.q_proj.bias\"]\n",
    "q_layer0_bias = q_layer0_bias.reshape(n_heads, -1)\n",
    "print(q_layer0_bias.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### im going to implement the first head of the first layer\n",
    "here i access the query weight matrix first head of the first layer, the size of this query weight matrix is [128x2560]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 201,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([128, 2560])"
      ]
     },
     "execution_count": 201,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "q_layer0_head0 = q_layer0[0]\n",
    "q_layer0_head0.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 202,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([128])"
      ]
     },
     "execution_count": 202,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "q_layer0_bias_head0 = q_layer0_bias[0]\n",
    "q_layer0_bias_head0.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### we now multiply the query weights with the token embedding, to recive a query for the token\n",
    "here you can see the resulting shape is [7x128], this is because we have 7 tokens and for each token there is a 128 length query."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 249,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.bfloat16\n",
      "torch.bfloat16\n",
      "torch.bfloat16\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 128])"
      ]
     },
     "execution_count": 249,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "q_per_token = torch.matmul(token_embeddings, q_layer0_head0.T) + q_layer0_bias_head0\n",
    "q_per_token.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## positioning encoding\n",
    "we are now at a stage where we have a query vector for each token in our prompt, but if you think about it -- the indivitually query vector has no idea about the position in the prompt.\n",
    "<br><br>\n",
    "query: \"the answer to the ultimate question of life, the universe, and everything is \"\n",
    "<br><br>\n",
    "in our prompt we have used \"the\" three times, we need the query vectors of all 3 \"the\" tokens to have different query vectors (each of size [1x128]) based on their positions in the query. we perform these rotations using RoPE (rotory positional embedding).\n",
    "<br><br>\n",
    "### RoPE\n",
    "watch this video (this is what i watched) to understand the math.\n",
    "https://www.youtube.com/watch?v=o29P0Kpobz0&t=530s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 204,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 64, 2])"
      ]
     },
     "execution_count": 204,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "q_per_token_split_into_pairs = q_per_token.float().view(q_per_token.shape[0], -1, 2)\n",
    "q_per_token_split_into_pairs.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "in the above step, we split the query vectors into pairs, we apply a rotational angle shift to each pair!\n",
    "<br><br>\n",
    "we now have a vector of size [7x64x2], this is the 128 length queries split into 64 pairs for each token in the prompt! each of those 64 pairs will be rotated by m*(theta) where m is the position of the token for which we are rotating the query!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## using dot product of complex numbers to rotate a vector"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 205,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([0.0000, 0.0156, 0.0312, 0.0469, 0.0625, 0.0781, 0.0938, 0.1094, 0.1250,\n",
       "        0.1406, 0.1562, 0.1719, 0.1875, 0.2031, 0.2188, 0.2344, 0.2500, 0.2656,\n",
       "        0.2812, 0.2969, 0.3125, 0.3281, 0.3438, 0.3594, 0.3750, 0.3906, 0.4062,\n",
       "        0.4219, 0.4375, 0.4531, 0.4688, 0.4844, 0.5000, 0.5156, 0.5312, 0.5469,\n",
       "        0.5625, 0.5781, 0.5938, 0.6094, 0.6250, 0.6406, 0.6562, 0.6719, 0.6875,\n",
       "        0.7031, 0.7188, 0.7344, 0.7500, 0.7656, 0.7812, 0.7969, 0.8125, 0.8281,\n",
       "        0.8438, 0.8594, 0.8750, 0.8906, 0.9062, 0.9219, 0.9375, 0.9531, 0.9688,\n",
       "        0.9844])"
      ]
     },
     "execution_count": 205,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "zero_to_one_split_into_64_parts = torch.tensor(range(64))/64\n",
    "zero_to_one_split_into_64_parts"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 206,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([1.0000e+00, 7.8583e-01, 6.1753e-01, 4.8527e-01, 3.8134e-01, 2.9967e-01,\n",
       "        2.3549e-01, 1.8505e-01, 1.4542e-01, 1.1428e-01, 8.9802e-02, 7.0569e-02,\n",
       "        5.5455e-02, 4.3578e-02, 3.4245e-02, 2.6911e-02, 2.1147e-02, 1.6618e-02,\n",
       "        1.3059e-02, 1.0262e-02, 8.0644e-03, 6.3372e-03, 4.9800e-03, 3.9134e-03,\n",
       "        3.0753e-03, 2.4167e-03, 1.8991e-03, 1.4924e-03, 1.1727e-03, 9.2157e-04,\n",
       "        7.2420e-04, 5.6910e-04, 4.4721e-04, 3.5143e-04, 2.7617e-04, 2.1702e-04,\n",
       "        1.7054e-04, 1.3402e-04, 1.0531e-04, 8.2759e-05, 6.5034e-05, 5.1106e-05,\n",
       "        4.0161e-05, 3.1559e-05, 2.4800e-05, 1.9489e-05, 1.5315e-05, 1.2035e-05,\n",
       "        9.4574e-06, 7.4319e-06, 5.8402e-06, 4.5894e-06, 3.6065e-06, 2.8341e-06,\n",
       "        2.2271e-06, 1.7501e-06, 1.3753e-06, 1.0808e-06, 8.4929e-07, 6.6740e-07,\n",
       "        5.2446e-07, 4.1214e-07, 3.2387e-07, 2.5451e-07])"
      ]
     },
     "execution_count": 206,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "freqs = 1.0 / (rope_theta ** zero_to_one_split_into_64_parts)\n",
    "freqs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 207,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[ 0,  0,  0,  0,  0,  0,  0],\n",
       "        [ 0,  1,  2,  3,  4,  5,  6],\n",
       "        [ 0,  2,  4,  6,  8, 10, 12],\n",
       "        [ 0,  3,  6,  9, 12, 15, 18],\n",
       "        [ 0,  4,  8, 12, 16, 20, 24],\n",
       "        [ 0,  5, 10, 15, 20, 25, 30],\n",
       "        [ 0,  6, 12, 18, 24, 30, 36]])"
      ]
     },
     "execution_count": 207,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "abc = torch.arange(len(tokens))\n",
    "abc.shape\n",
    "torch.outer(abc, abc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 208,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([7, 64])\n",
      "torch.Size([7, 64])\n"
     ]
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "freqs_for_each_token = torch.outer(torch.arange(len(tokens)), freqs)\n",
    "print(freqs_for_each_token.shape)\n",
    "freqs_cis = torch.polar(torch.ones_like(freqs_for_each_token), freqs_for_each_token)\n",
    "print(freqs_cis.shape)\n",
    "\n",
    "# viewing tjhe third row of freqs_cis\n",
    "value = freqs_cis[3]\n",
    "plt.figure()\n",
    "for i, element in enumerate(value[:len(tokens)]):\n",
    "    plt.plot([0, element.real], [0, element.imag], color='blue', linewidth=1, label=f\"Index: {i}\")\n",
    "    plt.annotate(f\"{i}\", xy=(element.real, element.imag), color='red')\n",
    "plt.xlabel('Real')\n",
    "plt.ylabel('Imaginary')\n",
    "plt.title('Plot of one row of freqs_cis')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### now that we have a complex number (the angle change vector) for every token's query element\n",
    "we can convert our queries (the one we split into pairs) as complex numbers and then dot product to rotate the query based on the position\n",
    "<br>\n",
    "honeslty this is beautiful to think about :)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 209,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 64])"
      ]
     },
     "execution_count": 209,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "q_per_token_as_complex_numbers = torch.view_as_complex(q_per_token_split_into_pairs)\n",
    "q_per_token_as_complex_numbers.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 210,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 64])"
      ]
     },
     "execution_count": 210,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "q_per_token_as_complex_numbers_rotated = q_per_token_as_complex_numbers * freqs_cis\n",
    "q_per_token_as_complex_numbers_rotated.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### after rotated vector is obtained\n",
    "we can get back our the queries as pairs by viewing the complex numbers as real numbers again"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 211,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 64, 2])"
      ]
     },
     "execution_count": 211,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "q_per_token_split_into_pairs_rotated = torch.view_as_real(q_per_token_as_complex_numbers_rotated)\n",
    "q_per_token_split_into_pairs_rotated.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "the rotated pairs are now merged, we now have a new query vector (rotated query vector) that is of the shape [7x128] where 7 is the number of tokens and the 128 is the dim of the query vector"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 212,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 128])"
      ]
     },
     "execution_count": 212,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "q_per_token_rotated = q_per_token_split_into_pairs_rotated.view(q_per_token.shape)\n",
    "q_per_token_rotated.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# keys (almost the same as queries)\n",
    "im lazy as fuck, so im not going to go through the math for keys, the only things you need to keep in mind are:\n",
    "<br>\n",
    "&gt; keys generate key vectors also of dimention 128\n",
    "<br>\n",
    "&gt; the number of keys equal to number of queries, qwen1.5-4B use MHA, instead of GQA\n",
    "<br>\n",
    "&gt; keys are also rotated to add positional info, just like queries because of the same reasons "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 213,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([20, 128, 2560])\n",
      "torch.Size([20, 128])\n"
     ]
    }
   ],
   "source": [
    "k_layer0 = model[\"model.layers.0.self_attn.k_proj.weight\"]\n",
    "k_layer0 = k_layer0.view(n_kv_heads, 2, k_layer0.shape[0] // n_kv_heads//2, dim).permute(0,2,1,3).reshape(n_kv_heads, k_layer0.shape[0]// n_kv_heads, dim)\n",
    "print(k_layer0.shape)\n",
    "\n",
    "k_layer0_bias = model[\"model.layers.0.self_attn.k_proj.bias\"]\n",
    "k_layer0_bias = k_layer0_bias.reshape(n_heads, -1)\n",
    "print(k_layer0_bias.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 214,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([128, 2560])"
      ]
     },
     "execution_count": 214,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "k_layer0_head0 = k_layer0[0]\n",
    "k_layer0_head0.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 215,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([128])"
      ]
     },
     "execution_count": 215,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "k_layer0_bias_head0 = k_layer0_bias[0]\n",
    "k_layer0_bias_head0.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 216,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 128])"
      ]
     },
     "execution_count": 216,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "k_per_token = torch.matmul(token_embeddings, k_layer0_head0.T) + k_layer0_bias_head0\n",
    "k_per_token.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 217,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 64, 2])"
      ]
     },
     "execution_count": 217,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "k_per_token_split_into_pairs = k_per_token.float().view(k_per_token.shape[0], -1, 2)\n",
    "k_per_token_split_into_pairs.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 218,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 64])"
      ]
     },
     "execution_count": 218,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "k_per_token_as_complex_numbers = torch.view_as_complex(k_per_token_split_into_pairs)\n",
    "k_per_token_as_complex_numbers.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 219,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 64, 2])"
      ]
     },
     "execution_count": 219,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "k_per_token_split_into_pairs_rotated = torch.view_as_real(k_per_token_as_complex_numbers * freqs_cis)\n",
    "k_per_token_split_into_pairs_rotated.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 220,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 128])"
      ]
     },
     "execution_count": 220,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "k_per_token_rotated = k_per_token_split_into_pairs_rotated.view(k_per_token.shape)\n",
    "k_per_token_rotated.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## at this stage now have both the rotated values of queries and keys, for each token. \n",
    "each of the queries and keys are now of shape [7x128]. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## in the next step we will multiply the queries and key matrices\n",
    "doing this will give us a score mapping each token with one another\n",
    "<br>\n",
    "this score describes how well each token's query relates to the each tokens's key. \n",
    "THIS IS SELF ATTENTION :)\n",
    "<br>\n",
    "the shape of the attention score matrix (qk_per_token) is [7x7] where 7 is the number of tokens in the prompt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 221,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 7])"
      ]
     },
     "execution_count": 221,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "qk_per_token = torch.matmul(q_per_token_rotated, k_per_token_rotated.T)/(head_dim)**0.5\n",
    "qk_per_token.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# we now have to mask query key scores\n",
    "during the training process of qwen, the future token qk scores are masked.\n",
    "<br>\n",
    "why? because during training we only learn to predict tokens using past tokens.\n",
    "<br>\n",
    "as a result, during inference we set the future tokens to zero."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 222,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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lK9D8Di/k6d9HYLnGT6a1edF33/S1NcheIyjvKxmd+iBX4eSkhK3x3a7XXa7/bL1PvroI/Xq1UuDBw/Wp59+qptvvlnjxo3T2LFjq7S/WvSfDQAAXI+4uDhFRka6lrlz51a63j/+8Q8tWLBALVu21Pr16/X73/9eTz75pBYtWlSl/dW+j54AANRWHpqzUVxcrIiICNfTlVU1JMnpdCo1NVVz5syRJN1222368ssv9ac//UkjR4687t1S2QAAwCIuztkws0hSRESE23KlZCM2NlZt2rRxey4pKUlFRUVViptkAwAAVCotLU379+93e+7AgQOKj4+v0jgkGwAAWIXhgaUKnn76aW3fvl1z5szRN998ow8++EDvvPOOnnjiiSqNQ7IBAIBFeKqNcr06deqklStXavHixWrXrp1mzZql119/XRkZGVUahwmiAADgin7zm9/oN7/5jakxSDYAALAKi95inmQDAACrsGiywZwNAADgVVQ2AACwiJq+N4qnkGwAAGAVFm2jkGwAAGAVFk02mLMBAAC8isoGAAAWwZwNAADgXbRRAAAALkdlAwAAi6CNAgAAvIs2CgAAwOWobAAAYBUWrWyQbAAAYBG2/1vMbO8LtFE8aNSoUerfv7+vwwAA4IZSJ5KNc+fO+ToEAADMMzyw+MANlWzk5OQoKipKq1atUsuWLRUUFKRevXqpuLjYbb2//vWvSklJUVBQkJo1a6bs7GydP3/e9brNZtOCBQvUt29fhYaGavbs2ZKk//7v/1anTp0UFBSkhg0basCAAa5tysvLNXnyZN18880KDQ3VHXfcoS1btlwW2/r165WUlKSwsDD17t1bR44ckSRlZWVp0aJF+utf/yqbzSabzea2PQAAZl089dXM4gs3VLIhSWfOnNHs2bP17rvvKjc3VydOnNCwYcNcr2/dulUjRozQU089pa+++kpvv/22cnJyXAnFRVlZWRowYIDy8/M1ZswYrVmzRgMGDFCfPn20d+9ebdq0SZ07d3atP378eH3++edasmSJvvjiCw0ePFi9e/fWwYMH3WJ79dVX9d577+mzzz5TUVGRJk+eLEmaPHmyhgwZ4kpAjhw5ol/96leVHmN5eblKSkrcFgAArsmilY0bboLouXPnNH/+fN1xxx2SpEWLFikpKUk7d+5U586dlZ2dreeff14jR46UJDVr1kyzZs3S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p2effda44447vH8gV1GVysaECROMHj16uKo5VvPLykZ4eLiRk5Pju4Cuw8MPP2zcc889bs89++yzRps2bVyP4+PjjUceecT12Ol0GjfddJOxYMECwzAM47333jNatWrl9nMrLy83goODjfXr11c7toufurdv3+56rqCgwJBk7NixwzAMw0hPT7+s+vXee+8ZsbGxrseSjIkTJ7qt06VLFyMjI6PS/R4+fNjw9/c3/vnPf7o9n56ebkydOtUttm+++cb1ODg42GjcuLFhGBfejwEBAUafPn1c21/6ftyzZ48hyfj2228rjWHkyJFGgwYNjNOnT7ueW7BggREWFmY4HA7DMCqvbFxanbmSV155xbj99ttdjy9WRX/p0vfrO++8Y9SvX9+tArtmzRrDz8/POHr0qCvm+Ph44/z58651Bg8ebAwdOvSaMRmGYdx///3GpEmTXI/vvvtu49e//rXbOp06dTKmTJliGIZhrF+/3ggICHD7Oa1bt47KhpdQ2aiFdu7cqby8PLVt21bl5eXat2+fSktLFR0drbCwMNdSWFioQ4cOubZLSEhwmywWGxurY8eO+eIQqmXUqFHKy8tTq1at9OSTT2rDhg2+DsmUZ555Ro899ph69uypF1980e1ndaMoKChQWlqa23NpaWk6ePCgHA6H67n27du7vrbZbIqJiXH9bu3bt0/ffPONwsPDXb+bDRo0UFlZmeljDggIUKdOnVyPW7duraioKBUUFLj2/cILL7i9L8aOHasjR47ozJkzru1SU1Pdxs3Ly1N6enql+8zPz5fD4VBiYqLbuJ9++qnb8YSEhKh58+Zu35dL32+hoaFulaxL348dOnRQenq6kpOTNXjwYP35z3/Wzz//7BZHhw4d3CaHd+nSRaWlpSouLr7i9+uXxylJS5cuVVpammJiYhQWFqbp06erqKjoimNUpqCgQB06dHCrwKalpcnpdLoqXJLUtm1b+fv7V3rMl3I4HJo1a5aSk5PVoEEDhYWFaf369ZfFdenv3S/HKygoUFxcnJo0aeJ6vUuXLlU6Llw/JohaWIsWLWSz2dzerJLUrFkzSXJNOCstLVVsbKy2bNly2RiXnm73yxK9zWaT0+n0bNAm2Gw2tzKxdKEEf1FKSooKCwu1bt06ffLJJxoyZIh69uypDz/8sKZD9YisrCw9/PDDWrNmjdatW6fMzEwt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",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def display_qk_heatmap(qk_per_token):\n",
    "    _, ax = plt.subplots()\n",
    "    im = ax.imshow(qk_per_token.to(float).detach(), cmap='viridis')\n",
    "    ax.set_xticks(range(len(prompt_split_as_tokens)))\n",
    "    ax.set_yticks(range(len(prompt_split_as_tokens)))\n",
    "    ax.set_xticklabels(prompt_split_as_tokens)\n",
    "    ax.set_yticklabels(prompt_split_as_tokens)\n",
    "    ax.figure.colorbar(im, ax=ax)\n",
    "    \n",
    "display_qk_heatmap(qk_per_token)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 223,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[0., -inf, -inf, -inf, -inf, -inf, -inf],\n",
       "        [0., 0., -inf, -inf, -inf, -inf, -inf],\n",
       "        [0., 0., 0., -inf, -inf, -inf, -inf],\n",
       "        [0., 0., 0., 0., -inf, -inf, -inf],\n",
       "        [0., 0., 0., 0., 0., -inf, -inf],\n",
       "        [0., 0., 0., 0., 0., 0., -inf],\n",
       "        [0., 0., 0., 0., 0., 0., 0.]])"
      ]
     },
     "execution_count": 223,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mask = torch.full((len(tokens), len(tokens)), float(\"-inf\"), device=tokens.device)\n",
    "mask = torch.triu(mask, diagonal=1)\n",
    "mask"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 224,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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Pf/885o7d668vCouxPTo0UNTpkxxWjdz5kzHxzExMZo6daqWL1+uF154QQEBAQoODr7p5TwffPCBSkpK9O677yooKEiStGjRIj3yyCN6+eWX1bBhQ0lSvXr1tGjRInl7e6tVq1bq27evtm7det1kIzMzUxkZGTf6NAIAcK1qvoPot99+q8cee0w//vijGjRooF//+tfatWuXGjRoUKlxXJ5sxMTEOBINSYqMjNSpU6ckSe3bt1dycrLi4+PVq1cvPfTQQxo8eLDThMz27dsrMDDQ8bpLly4qLi5WYWGhI0H5pcTExGvWrVixQm+++aaOHTum4uJiXbp0qdL9qby8PLVv396RaEhSUlKS7Ha7Dh065Eg22rRpI29vb6dzPnjw4HXHnT59up577jnH66KiIkVFRVUqNgBA7VPdD2Jbvnx51Q92FZffZ+OXV4rYbDbZ7XZJkre3t7Zs2aKNGzeqdevWeuutt9SyZUvl5+ebOubVyYAkff7550pNTVWfPn20bt06HThwQDNmzHCa++FKNzrnivj5+Tkm51R1kg4AAFZR7Tf1stlsSkpKUkZGhg4cOCBfX1+tWbPG8X5ubq4uXLjgeL1r1y4FBwdX6j//nTt3Kjo6WjNmzFBiYqJatGih48ePO23j6+ur8vLyG44TFxen3NxcxwRWScrOzpaXl5datmx5y/EAAOAShgsWD6jWZGP37t2aP3++9u3bp4KCAq1evVrff/+94uLiHNuUlZXpySef1FdffaUNGzYoLS1NEyZMuO58jYq0aNFCBQUFWr58uY4dO6Y333zTKaGRLrd78vPzlZOTox9++KHCu6elpqbK399fI0eO1Jdffqlt27Zp4sSJGj58uKOFAgBAdbEZhunFE6o12QgNDdVnn32mPn36KDY2VjNnztTChQudbhqSnJysFi1a6P7771dKSor69eun9PT0Sh2nX79+evbZZzVhwgR16NBBO3fu1KxZs5y2efTRR9W7d291795dDRo00LJly64ZJzAwUJs2bdLp06fVqVMnDR48WMnJyVq0aFGVzh8AgNrIZhgeSnMqMGrUKJ05c6bW3S68qKhIYWFh6tlkony8Kr6LmxUZ3jXv0Tv/6l6zKlrhB897OgSX8/75+s94sCrbxRu3fK3o47/P93QILnPld/jZs2fdNgfvyjG63p8mHx//Ko9z6VKJdnyW4dZY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Y/PmzRX+noF5zNmwsB9//FGbN2/W+PHjFRQUVOE2Vx7sNmTIEJ06dUobN27U/v37lZCQoOTkZJ0+fdqx7bFjx7R27VqtW7dO69at06effqqXXnqpWs7FFd5880199NFHWrlypQ4dOqT3339fMTExng6rylJTU3X33Xdr79692r9/v1588UWX/0dt1v79+zV06FANGzZMBw8eVHp6umbNmqWsrCyn7RYuXKjExEQdOHBA48aN0+9//3sdOnRI0uVKQa9evRQSEqIdO3YoOztbwcHB6t27t+mHTp0/f17z5s3Tu+++q+zsbJ05c0bDhg1zvL9jxw6NGDFCzzzzjL766iu9/fbbysrK0rx585zGSU9P18CBA3Xw4EGNGTNG69ev18CBA9WnTx8dOHBAW7duVefOnR3bT5gwQZ9//rmWL1+uL774QkOGDFHv3r115MgRp9gWLFig9957T9OnT5fdbtfUqVMlSb/61a/k4+Ojhg0batu2bVqzZo3y8vIcP48nTpzQY489pjFjxigvL0/bt2/XoEGDnB5xvnXrVsd7y5Yt0+rVq5WRkXHDz9eCBQvUvn17HThwQLNmzZIkhYSEKCsrS1999ZXeeOMN/fnPf9Yf/vAHSVJKSoqmTJmiNm3a6MSJEzpx4oRSUlKuGffnn39Wr169VK9ePe3du1erVq3SJ598ogkTJjhtt23bNh07dkzbtm3T0qVLlZWVdc33knT5gWT33HOP1q9fry+//FK//e1vNXz4cO3Zs8dpu6VLlyooKEi7d+/WK6+8ojlz5mjLli2SLj8Ac9CgQfL19dXu3bv1pz/9SdOmTbvh5wcmeDrbQdXt2rXLkGSsXr3aaX14eLgRFBRkBAUFGS+88IKxY8cOIzQ01CgpKXHarlmzZo7/MNPS0ozAwECn/5yef/55495773X/idxAZSobEydONHr06OGo5ljNLysbISEhRlZWlucCugWPP/648eCDDzqte/75543WrVs7XkdHRxtPPPGE47XdbjfuvPNOY/HixYZhGMZ7771ntGzZ0unrVlpaagQEBBibNm2qcmxX/uvetWuXY11eXp4hydi9e7dhGIaRnJx8TfXrvffeMyIjIx2vJRmTJ0922qZLly5Gampqhcc9fvy44e3tbfzzn/90Wp+cnGxMnz7dKbajR486XgcEBBgNGzY0DOPyz6OPj4/Rp08fx/5X/zzu37/fkGR88803FcYwcuRIo379+sbPP//sWLd48WIjODjYKC8vNwyj4srG1dWZ63n11VeNe+65x/H6SlX0l67+eX3nnXeMevXqOVVg169fb3h5eRknT550xBwdHW1cunTJsc2QIUOMlJSUm8ZkGIbRt29fY8qUKY7XDzzwgPHrX//aaZtOnToZ06ZNMwzDMDZt2mT4+Pg4fZ02btxIZcNNqGzUQHv27FFOTo7atGmj0tJS5ebmqri4WOHh4QoODnYs+fn5OnbsmGO/mJgYp8likZGROnXqlCdOoUpGjRqlnJwctWzZUpMmTdLmzZs9HZIpzz33nJ566in17NlTL730ktPX6naRl5enpKQkp3VJSUk6cuSIysvLHevatWvn+NhmsykiIsLxvZWbm6ujR48qJCTE8b1Zv359lZSUmD5nHx8fderUyfG6VatWqlu3rvLy8hzHnjNnjtPPxdixY3XixAmdP3/esV9iYqLTuDk5OUpOTq7wmAcPHlR5ebliY2Odxv3000+dzicwMFDNmjVz+rxc/fMWFBTkVMm6+uexffv2Sk5OVnx8vIYMGaI///nP+umnn5ziaN++vdPk8C5duqi4uFiFhYXX/Xz98jwlacWKFUpKSlJERISCg4M1c+ZMFRQUXHeMiuTl5al9+/ZOFdikpCTZ7XZHhUuS2rRpI29v7wrP+Wrl5eWaO3eu4uPjVb9+fQUHB2vTpk3XxHX1990vx8vLy1NUVJQaNWrkeL9Lly6VOi/cOiaIWljz5s1ls9mcflglqWnTppLkmHBWXFysyMhIbd++/Zoxrr7c7pclepvNJrvd7tqgTbDZbE5lYulyCf6KhIQE5efna+PGjfrkk080dOhQ9ezZUx9++GF1h+oS6enpevzxx7V+/Xpt3LhRaWlpWr58uQYOHOjp0CrtRt9bxcXFuueee/T+++9fs1+DBg3cGldxcbEyMjI0aNCga967+lHev2xT3mgyZ3Fxsby9vbV//36nP5ySFBwc7Pi4opbY1d/fXl7O/wte/Tnz9vbWli1btHPnTm3evFlvvfWWZsyYod27d6tJkybXje1mfnmen3/+uVJTU5WRkaFevXopLCxMy5cv18KFC6t8jBu51d9Br776qt544w29/vrrio+PV1BQkCZPnnxN2+12/51Wm5BsWFh4eLgefPBBLVq0SBMnTrzuvI2EhASdPHlSPj4+lp7D0KBBA504ccLx+siRI07/fUpSaGioUlJSlJKSosGDB6t37946ffq0Za9qiY2NVWxsrJ599lk99thjWrJkyW2VbMTFxSk7O9tpXXZ2tmJjY6/5Q3s9CQkJWrFihe68806Fhoa6NL5Lly5p3759jvkUhw4d0pkzZxQXF+c49qFDh9S8efNKjduuXTtt3bpVo0ePvua9jh07qry8XKdOnVLXrl2rHLvNZnOqDlX0flJSkpKSkjR79mxFR0drzZo1eu655yRdrtpcuHDBkRjt2rVLwcHBioqKuuUYdu7cqejoaM2YMcOx7vjx407b+Pr63jBO6fL3SVZWln7++WfH76ns7Gx5eXmpZcuWtxzPFdnZ2erfv7+eeOIJSZfnXxw+fFitW7e+5THi4uJUWFioEydOKDIyUtLlzxHcgzaKxf3xj3/UpUuXlJiYqBUrVigvL0+HDh3SX/7yF3399dfy9vZWz5491aVLFw0YMECbN2/WN998o507d2rGjBnat2+fp0/hlvXo0UOLFi3SgQMHtG/fPv3ud79z+s/ltdde07Jly/T111/r8OHDWrVqlSIiIix5o6wLFy5owoQJ2r59u44fP67s7Gzt3bvX8UfydjFlyhRt3bpVc+fO1eHDh7V06VItWrTIMdHxVqSmpuqOO+5Q//79tWPHDuXn52v79u2aNGmSvv32W1Px1alTRxMnTtTu3bu1f/9+jRo1Svfdd58j+Zg9e7beffddZWRk6O9//7vy8vK0fPlyzZw584bjpqWladmyZUpLS1NeXp4OHjyol19+WdLlBDE1NVUjRozQ6tWrlZ+frz179jjuhXOrfH199cUXX+jQoUP64YcfnP6g7969W/Pnz9e+fftUUFCg1atX6/vvv3f6/igrK9OTTz6pr776Shs2bFBaWpomTJhwTcXkRlq0aKGCggItX75cx44d05tvvqk1a9Y4bRMTE6P8/Hzl5OTohx9+UGlp6TXjpKamyt/fXyNHjtSXX36pbdu2aeLEiRo+fLgaNmx4y/FcHdeVyk5eXp6efvpp/etf/6rUGD179lRsbKxGjhyp3Nxc7dixwympgmuRbFhcs2bNdODAAfXs2VPTp09X+/btlZiYqLfeektTp07V3LlzZbPZtGHDBt1///0aPXq0YmNjNWzYMB0/frxKP+iesnDhQkVFRalr1656/PHHNXXqVKeedEhIiF555RUlJiaqU6dO+uabb7Rhw4ZK/XK9XXh7e+vHH3/UiBEjFBsbq6FDh+rhhx++6dUE1S0hIUErV67U8uXL1bZtW82ePVtz5szRqFGjbnmMwMBAffbZZ2rcuLEGDRqkuLg4PfnkkyopKTFd6QgMDNS0adP0+OOPKykpScHBwVqxYoXj/V69emndunXavHmzOnXqpPvuu09/+MMfFB0dfcNxu3XrplWrVumjjz5Shw4d1KNHD6crIZYsWaIRI0ZoypQpatmypQYMGKC9e/eqcePGtxx7eHi4WrZsqcTERDVo0ED5+fmO90JDQ/XZZ5+pT58+io2N1cyZM7Vw4UI9/PDDjm2Sk5PVokUL3X///UpJSVG/fv2Unp5+y8eXpH79+unZZ5/VhAkT1KFDB+3cudNxlcoVjz76qHr37q3u3burQYMGWrZs2TXjBAYGatOmTTp9+rQ6deqkwYMHKzk5WYsWLapUPFfMnDlTCQkJ6tWrl7p166aIiIhK34TLy8tLa9as0YULF9S5c2c99dRT11yFBNfhDqIAaqSsrCxNnjxZZ86c8XQo1c7ddx8FKst6//IBAABLIdkAAABuRRsFAAC4FZUNAADgViQbAADArUg2AACAW5FsAAAAtyLZAAAAbkWyAQAA3IpkAwAAuBXJBgAAcCuSDQAA4Fb/H+lLC3/x967BAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "qk_per_token_after_masking = qk_per_token + mask\n",
    "display_qk_heatmap(qk_per_token_after_masking)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 225,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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AADwOCQoAADA43h9gvLf//3fuv766xUQEKDY2FjNmjXL4f3Y2FjNmDFDDz/8sEJDQ9WsWTO99tprDscUFxerX79+ioiIUP369XX33Xdr3759lfZZVlam0tJShw0AgMux6P/moVzR5u4LqAavTlC2bt2qfv36qX///tqxY4dycnI0adIk5ebmOhw3a9YsJSUladu2bRo+fLgee+wx7dy5U5J05swZpaWlKTQ0VGvXrtW6desUEhKinj17qry8vMJ+Z86cqfDwcPsWExNT05cKALganL/N2JnNJLw6QZk9e7Z69OihSZMmKS4uToMHD1ZmZqaee+45h+N69eql4cOHq2XLlho3bpwaNmyoNWvWSJIWLVokm82mv//972rXrp3i4+M1f/58FRUVKT8/v8J+x48fr2PHjtm34uLimr5UAABMxasTlMLCQqWkpDjsS0lJ0e7du2W1Wu372rdvb//YYrEoKipKhw4dkiRt375d3377rUJDQxUSEqKQkBDVr19fp0+f1p49eyrsNyAgQGFhYQ4bAACXZbhgMwluM66COnXqOLy2WCyy2WySpBMnTqhjx456++23L2rXqFGjWokPAOAlvOg2Y69OUOLj47Vu3TqHfevWrVNcXJx8fX2rdI7ExEQtWrRIjRs3phICAICLePUQz5NPPqm8vDxNmzZNu3bt0oIFCzR37lyNGTOmyucYMGCAGjZsqLvvvltr167V3r17lZ+fryeeeELff/99DUYPAPA2Tt3B4+QqtLXNqxOUxMREvffee1q4cKFuuOEGTZ48WVOnTtXgwYOrfI7g4GB99tlnatasmfr27av4+Hg98sgjOn36NBUVAIBrMQfFe9x777269957K32/ovVMCgoKHF5HRUVpwYIFLo4MAADv5fUJCgAApsEkWQAA4Gm86WnGXj0HBQAAeCYqKAAAmIWzy9WbaKl7EhQAAMyCOSgAAMDTMAcFAADAjaigAABgFgzxAAAAj+PscvUmSlAY4gEAAB6HCgoAAGbBEA8AAPA4XpSgMMQDAAA8DhUUAABMgnVQAAAA3IgEBQAAeByGeAAAMAsvmiRLggIAgEl40xwUEhRP0jRK8g1wdxQuYzlz1t0huNzIj29wdwgu1aZJqbtDcDm/sFB3h+ByZ7/b5+4Q4ElMlGQ4gzkoAADA41BBAQDALJiDAgAAPI03zUFhiAcAAHgc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",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "qk_per_token_after_masking_after_softmax = torch.nn.functional.softmax(qk_per_token_after_masking, dim=1).to(torch.bfloat16)\n",
    "display_qk_heatmap(qk_per_token_after_masking_after_softmax)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## values (almost the end of attention)\n",
    "these scores (0-1) are used to determine how much of value matrix is used per token\n",
    "<br>\n",
    "&gt; just like keys, value weights are also shared acorss every 4 attention heads (to save computation)\n",
    "<br>\n",
    "&gt; as a result, the shape of the value weight matrix below is [20x128x2560]\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 226,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([20, 128, 2560])\n",
      "torch.Size([20, 128])\n"
     ]
    }
   ],
   "source": [
    "v_layer0 = model[\"model.layers.0.self_attn.v_proj.weight\"]\n",
    "v_layer0 = v_layer0.view(n_kv_heads, v_layer0.shape[0] // n_kv_heads, dim)\n",
    "print(v_layer0.shape)\n",
    "\n",
    "v_layer0_bias = model[\"model.layers.0.self_attn.v_proj.bias\"]\n",
    "v_layer0_bias = v_layer0_bias.reshape(n_heads, -1)\n",
    "print(v_layer0_bias.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "the first layer, first head value weight matrix is given below"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 227,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([128, 2560])"
      ]
     },
     "execution_count": 227,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "v_layer0_head0 = v_layer0[0]\n",
    "v_layer0_head0.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 228,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([128])"
      ]
     },
     "execution_count": 228,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "v_layer0_bias_head0 = v_layer0_bias[0]\n",
    "v_layer0_bias_head0.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## value vectors\n",
    "we now use the value weghts to get the attention values per token, this is of size [7x128] where 7 is the number of tokens in the prompt and 128 is the dim of the value vector per token"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 229,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 128])"
      ]
     },
     "execution_count": 229,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "v_per_token = torch.matmul(token_embeddings, v_layer0_head0.T) + v_layer0_bias_head0\n",
    "v_per_token.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## attention\n",
    "the resultant attention vector after multipying with the values per token is of shape [7*128]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 230,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 128])"
      ]
     },
     "execution_count": 230,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "qkv_attention = torch.matmul(qk_per_token_after_masking_after_softmax, v_per_token)\n",
    "qkv_attention.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# multi head attention\n",
    "WE NOW HAVE THE ATTENTION VALUE OF THE FIRST LAYER AND FIRST HEAD\n",
    "<br>\n",
    "now im going to run a loop and perform the exact same math as the cells above but for every head in the first layer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 231,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "20"
      ]
     },
     "execution_count": 231,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "qkv_attention_store = []\n",
    "GQA_num = n_heads // n_kv_heads # \n",
    "for head in range(n_heads):\n",
    "    q_layer0_head = q_layer0[head]\n",
    "    k_layer0_head = k_layer0[head//GQA_num] \n",
    "    v_layer0_head = v_layer0[head//GQA_num]\n",
    "    q_layer0_bias_head = q_layer0_bias[head]\n",
    "    k_layer0_bias_head = k_layer0_bias[head//GQA_num]\n",
    "    v_layer0_bias_head = v_layer0_bias[head//GQA_num]\n",
    "    q_per_token = torch.matmul(token_embeddings, q_layer0_head.T) + q_layer0_bias_head\n",
    "    k_per_token = torch.matmul(token_embeddings, k_layer0_head.T) + k_layer0_bias_head\n",
    "    v_per_token = torch.matmul(token_embeddings, v_layer0_head.T) + v_layer0_bias_head\n",
    "\n",
    "    q_per_token_split_into_pairs = q_per_token.float().view(q_per_token.shape[0], -1, 2)\n",
    "    q_per_token_as_complex_numbers = torch.view_as_complex(q_per_token_split_into_pairs)\n",
    "    q_per_token_split_into_pairs_rotated = torch.view_as_real(q_per_token_as_complex_numbers * freqs_cis[:len(tokens)])\n",
    "    q_per_token_rotated = q_per_token_split_into_pairs_rotated.view(q_per_token.shape)\n",
    "\n",
    "    k_per_token_split_into_pairs = k_per_token.float().view(k_per_token.shape[0], -1, 2)\n",
    "    k_per_token_as_complex_numbers = torch.view_as_complex(k_per_token_split_into_pairs)\n",
    "    k_per_token_split_into_pairs_rotated = torch.view_as_real(k_per_token_as_complex_numbers * freqs_cis[:len(tokens)])\n",
    "    k_per_token_rotated = k_per_token_split_into_pairs_rotated.view(k_per_token.shape)\n",
    "\n",
    "    qk_per_token = torch.matmul(q_per_token_rotated, k_per_token_rotated.T)/(128)**0.5\n",
    "    mask = torch.full((len(tokens), len(tokens)), float(\"-inf\"), device=tokens.device)\n",
    "    mask = torch.triu(mask, diagonal=1)\n",
    "    qk_per_token_after_masking = qk_per_token + mask\n",
    "    qk_per_token_after_masking_after_softmax = torch.nn.functional.softmax(qk_per_token_after_masking, dim=1).to(torch.bfloat16)\n",
    "    qkv_attention = torch.matmul(qk_per_token_after_masking_after_softmax, v_per_token)\n",
    "    qkv_attention = torch.matmul(qk_per_token_after_masking_after_softmax, v_per_token)\n",
    "    qkv_attention_store.append(qkv_attention)\n",
    "\n",
    "len(qkv_attention_store)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "we now have a the qkv_attention matrix for all 20 heads on the first layer, next im going to merge all attention scores into one large matrix of size [7x2560]\n",
    "<br>\n",
    "we are almost at the end :)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 232,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 2560])"
      ]
     },
     "execution_count": 232,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "stacked_qkv_attention = torch.cat(qkv_attention_store, dim=-1)\n",
    "stacked_qkv_attention.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# weight matrix, one of the final steps\n",
    "one of the last things to do for a layer 0 attention is, is to multiply the weight matrix of the "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 233,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([2560, 2560])"
      ]
     },
     "execution_count": 233,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "w_layer0 = model[\"model.layers.0.self_attn.o_proj.weight\"]\n",
    "w_layer0.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### this is a simple linear layer, so we just matmul"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 234,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 2560])"
      ]
     },
     "execution_count": 234,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "embedding_delta = torch.matmul(stacked_qkv_attention, w_layer0.T)\n",
    "embedding_delta.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "we now have the change in the embedding value after attention, that should be adding to the original token embeddings"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 235,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 2560])"
      ]
     },
     "execution_count": 235,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "embedding_after_edit = token_embeddings_unnormalized + embedding_delta\n",
    "embedding_after_edit.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## we normalize and then run a feed forward neural network through the embedding delta"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 236,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 2560])"
      ]
     },
     "execution_count": 236,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "embedding_after_edit_normalized = rms_norm(embedding_after_edit, model[\"model.layers.0.post_attention_layernorm.weight\"])\n",
    "embedding_after_edit_normalized.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## loading the ff weights and implementing the feed forward network\n",
    "in qwen1.5, they used a SwiGLU feedforward network, this network architecture is really good at adding non linearity when needed by the model.\n",
    "<br>\n",
    "its pretty standard to use this feed forward network architecture in llms these days"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 237,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 2560])"
      ]
     },
     "execution_count": 237,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "w1 = model[\"model.layers.0.mlp.gate_proj.weight\"]\n",
    "w2 = model[\"model.layers.0.mlp.down_proj.weight\"]\n",
    "w3 = model[\"model.layers.0.mlp.up_proj.weight\"]\n",
    "output_after_feedforward = torch.matmul(torch.functional.F.silu(torch.matmul(embedding_after_edit_normalized, w1.T)) * torch.matmul(embedding_after_edit_normalized, w3.T), w2.T)\n",
    "output_after_feedforward.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# WE FINALLY HAVE NEW EDITED EMBEDDINGS FOR EACH TOKEN AFTER THE FIRST LAYER\n",
    "just 39 more layers to go before we are done (one for loop away)\n",
    "<br>\n",
    "you can imagine this edited embedding as having information about all queries asked on the first layer\n",
    "<br>\n",
    "now each layer will encode more and more complex queries on the quesions asked, until we have an embedding that knows everything about the next token that we need."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 238,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 2560])"
      ]
     },
     "execution_count": 238,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "layer_0_embedding = embedding_after_edit+output_after_feedforward\n",
    "layer_0_embedding.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# god, everything all at once\n",
    "yep, this is it. everything we did before, all at once, for every single layer.\n",
    "<br>\n",
    "\n",
    "# have fun reading :)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 239,
   "metadata": {},
   "outputs": [],
   "source": [
    "k_cache, v_cache = [], []   # init k v cache"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 240,
   "metadata": {},
   "outputs": [],
   "source": [
    "final_embedding = token_embeddings_unnormalized\n",
    "GQA_num = n_heads // n_kv_heads\n",
    "for layer in range(n_layers):\n",
    "    k_cache.append([])\n",
    "    v_cache.append([])\n",
    "    qkv_attention_store = []\n",
    "    layer_embedding_norm = rms_norm(final_embedding, model[f\"model.layers.{layer}.input_layernorm.weight\"])\n",
    "    q_layer = model[f\"model.layers.{layer}.self_attn.q_proj.weight\"]\n",
    "    q_layer = q_layer.view(n_heads, 2, q_layer.shape[0] // n_heads // 2, dim).permute(0, 2, 1, 3).reshape(n_heads, q_layer.shape[0] // n_heads, dim)\n",
    "    q_layer_bias = model[f\"model.layers.{layer}.self_attn.q_proj.bias\"]\n",
    "    q_layer_bias = q_layer_bias.reshape(n_heads, -1)\n",
    "    k_layer = model[f\"model.layers.{layer}.self_attn.k_proj.weight\"]\n",
    "    k_layer = k_layer.view(n_kv_heads, 2, k_layer.shape[0] // n_kv_heads // 2, dim).permute(0, 2, 1, 3).reshape(n_kv_heads, k_layer.shape[0] // n_kv_heads, dim)\n",
    "    k_layer_bias = model[f\"model.layers.{layer}.self_attn.k_proj.bias\"]\n",
    "    k_layer_bias = k_layer_bias.reshape(n_heads, -1)\n",
    "    v_layer = model[f\"model.layers.{layer}.self_attn.v_proj.weight\"]\n",
    "    v_layer = v_layer.view(n_kv_heads, v_layer.shape[0] // n_kv_heads, dim)\n",
    "    v_layer_bias = model[f\"model.layers.{layer}.self_attn.v_proj.bias\"]\n",
    "    v_layer_bias = v_layer_bias.reshape(n_heads, -1)\n",
    "    for head in range(n_heads):\n",
    "        q_layer_head = q_layer[head]\n",
    "        k_layer_head = k_layer[head//GQA_num]\n",
    "        v_layer_head = v_layer[head//GQA_num]\n",
    "        q_layer_bias_head = q_layer_bias[head]\n",
    "        k_layer_bias_head = k_layer_bias[head//GQA_num]\n",
    "        v_layer_bias_head = v_layer_bias[head//GQA_num]\n",
    "        q_per_token = torch.matmul(layer_embedding_norm, q_layer_head.T) + q_layer_bias_head\n",
    "        k_per_token = torch.matmul(layer_embedding_norm, k_layer_head.T) + k_layer_bias_head\n",
    "        v_per_token = torch.matmul(layer_embedding_norm, v_layer_head.T) + v_layer_bias_head\n",
    "        if head % GQA_num == 0:   # qwen1.5-4B use MHA，GQA_num=1\n",
    "            v_cache[-1].append(v_per_token)   # cache v vector\n",
    "        q_per_token_split_into_pairs = q_per_token.float().view(q_per_token.shape[0], -1, 2)\n",
    "        q_per_token_as_complex_numbers = torch.view_as_complex(q_per_token_split_into_pairs)\n",
    "        q_per_token_split_into_pairs_rotated = torch.view_as_real(q_per_token_as_complex_numbers * freqs_cis)\n",
    "        q_per_token_rotated = q_per_token_split_into_pairs_rotated.view(q_per_token.shape)\n",
    "        k_per_token_split_into_pairs = k_per_token.float().view(k_per_token.shape[0], -1, 2)\n",
    "        k_per_token_as_complex_numbers = torch.view_as_complex(k_per_token_split_into_pairs)\n",
    "        k_per_token_split_into_pairs_rotated = torch.view_as_real(k_per_token_as_complex_numbers * freqs_cis)\n",
    "        k_per_token_rotated = k_per_token_split_into_pairs_rotated.view(k_per_token.shape)\n",
    "        if head % GQA_num == 0:   # \n",
    "            k_cache[-1].append(k_per_token)  # cache k vector\n",
    "        qk_per_token = torch.matmul(q_per_token_rotated, k_per_token_rotated.T)/(128)**0.5\n",
    "        mask = torch.full((len(token_embeddings_unnormalized), len(token_embeddings_unnormalized)), float(\"-inf\"))\n",
    "        mask = torch.triu(mask, diagonal=1)\n",
    "        qk_per_token_after_masking = qk_per_token + mask\n",
    "        qk_per_token_after_masking_after_softmax = torch.nn.functional.softmax(qk_per_token_after_masking, dim=1).to(torch.bfloat16)\n",
    "        qkv_attention = torch.matmul(qk_per_token_after_masking_after_softmax, v_per_token)\n",
    "        qkv_attention_store.append(qkv_attention)\n",
    "\n",
    "    stacked_qkv_attention = torch.cat(qkv_attention_store, dim=-1)\n",
    "    w_layer = model[f\"model.layers.{layer}.self_attn.o_proj.weight\"]\n",
    "    embedding_delta = torch.matmul(stacked_qkv_attention, w_layer.T)\n",
    "    embedding_after_edit = final_embedding + embedding_delta\n",
    "    embedding_after_edit_normalized = rms_norm(embedding_after_edit, model[f\"model.layers.{layer}.post_attention_layernorm.weight\"])\n",
    "    w1 = model[f\"model.layers.{layer}.mlp.gate_proj.weight\"] \n",
    "    w2 = model[f\"model.layers.{layer}.mlp.down_proj.weight\"]\n",
    "    w3 = model[f\"model.layers.{layer}.mlp.up_proj.weight\"]\n",
    "    output_after_feedforward = torch.matmul(torch.functional.F.silu(torch.matmul(embedding_after_edit_normalized, w1.T)) * torch.matmul(embedding_after_edit_normalized, w3.T), w2.T)\n",
    "    final_embedding = embedding_after_edit+output_after_feedforward"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 241,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([7, 128])\n",
      "torch.Size([7, 128])\n",
      "40\n",
      "20\n",
      "torch.Size([7, 128])\n",
      "torch.Size([7, 128])\n",
      "40\n",
      "20\n"
     ]
    }
   ],
   "source": [
    "# k v cache info\n",
    "print(k_cache[0][0].shape)\n",
    "print(k_cache[0][-1].shape)\n",
    "print(len(k_cache))\n",
    "print(len(k_cache[0]))\n",
    "print(v_cache[0][0].shape)\n",
    "print(v_cache[0][-1].shape)\n",
    "print(len(v_cache))\n",
    "print(len(v_cache[0]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# we now have the final embedding, the best guess the model could make about the next token\n",
    "the shape of the embedding is the same as regular token embeddings [7x2560] where 7 is the number of tokens and 2560 is the embedding dim"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 242,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([7, 2560])"
      ]
     },
     "execution_count": 242,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "final_embedding = rms_norm(final_embedding, model[\"model.norm.weight\"])\n",
    "final_embedding.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# finally, lets decode the embedding into the token value\n",
    "we will use the output decoder to convert the final embedding into a token"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 243,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([151936, 2560])"
      ]
     },
     "execution_count": 243,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model[\"lm_head.weight\"].shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# we use the embedding of the last token to predict the next value\n",
    "hopefully in our case: ninety "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 244,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([151936])"
      ]
     },
     "execution_count": 244,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "logits = torch.matmul(final_embedding[-1], model[\"lm_head.weight\"].T)\n",
    "#logits = torch.matmul(final_embedding, model[\"lm_head.weight\"].T)\n",
    "logits.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### the model predicted token number 77876 as the next token, is this the token number for ninety?\n",
    "IM HYPING YOU UP, this is the last cell of code, hopefully you had fun :)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 245,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor(77876)"
      ]
     },
     "execution_count": 245,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "next_token = torch.argmax(logits, dim=-1)\n",
    "next_token"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# lets fucking go"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 246,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "' ninety'"
      ]
     },
     "execution_count": 246,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tokenizer.decode([next_token.item()])\n",
    "#tokenizer.decode(list(next_token.numpy()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Prefill stage is finish, we get the k_cache and v_cache for input_token.\n",
    "we also get a output(next token), and we can use it as a input token for generat next token. This is the feature for Decoder only model.\n",
    "##### 预填充阶段推理完成，并生成第一个token。同时缓存了prompt阶段过程中生成的k和v，用于下一阶段进行decoder推理，进行自回归生成，同时加载前面生成的k和v，进行加速推理。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 247,
   "metadata": {},
   "outputs": [],
   "source": [
    "max_new_len = 6\n",
    "seq_len = len(tokens)\n",
    "GQA_num = n_heads // n_kv_heads # 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 248,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "77876\n",
      "token decode:  -nine\n",
      "85603\n",
      "token decode:   percent\n",
      "3266\n",
      "token decode:   pers\n",
      "7413\n",
      "token decode:  piration\n",
      "28479\n",
      "token decode:  .\n"
     ]
    }
   ],
   "source": [
    "next_token = torch.tensor([next_token.item()])\n",
    "for _ in range(max_new_len-1):\n",
    "    print(next_token[-1].item())\n",
    "    if next_token[-1].item() == 151643:    # 151643 is \"<|end_of_text|>\"\n",
    "        break\n",
    "    next_token = next_token[-1:]\n",
    "    next_token_embeddings_unnormalized = embedding_layer(next_token).to(torch.bfloat16)\n",
    "\n",
    "    final_embedding = next_token_embeddings_unnormalized\n",
    "    for layer in range(n_layers):\n",
    "        qkv_attention_store = []\n",
    "        layer_embedding_norm = rms_norm(final_embedding, model[f\"model.layers.{layer}.input_layernorm.weight\"])\n",
    "        q_layer = model[f\"model.layers.{layer}.self_attn.q_proj.weight\"]\n",
    "        q_layer = q_layer.view(n_heads, 2, q_layer.shape[0] // n_heads // 2, dim).permute(0, 2, 1, 3).reshape(n_heads, q_layer.shape[0] // n_heads, dim)\n",
    "        q_layer_bias = model[f\"model.layers.{layer}.self_attn.q_proj.bias\"]\n",
    "        q_layer_bias = q_layer_bias.reshape(n_heads, -1)\n",
    "        k_layer = model[f\"model.layers.{layer}.self_attn.k_proj.weight\"]\n",
    "        k_layer = k_layer.view(n_kv_heads, 2, k_layer.shape[0] // n_kv_heads // 2, dim).permute(0, 2, 1, 3).reshape(n_kv_heads, k_layer.shape[0] // n_kv_heads, dim)\n",
    "        k_layer_bias = model[f\"model.layers.{layer}.self_attn.k_proj.bias\"]\n",
    "        k_layer_bias = k_layer_bias.reshape(n_heads, -1)\n",
    "        v_layer = model[f\"model.layers.{layer}.self_attn.v_proj.weight\"]\n",
    "        v_layer = v_layer.view(n_kv_heads, v_layer.shape[0] // n_kv_heads, dim)\n",
    "        v_layer_bias = model[f\"model.layers.{layer}.self_attn.v_proj.bias\"]\n",
    "        v_layer_bias = v_layer_bias.reshape(n_heads, -1)\n",
    "        for head in range(n_heads):\n",
    "            q_layer_head = q_layer[head]\n",
    "            k_layer_head = k_layer[head//GQA_num]\n",
    "            v_layer_head = v_layer[head//GQA_num]\n",
    "            q_layer_bias_head = q_layer_bias[head]\n",
    "            k_layer_bias_head = k_layer_bias[head//GQA_num]\n",
    "            v_layer_bias_head = v_layer_bias[head//GQA_num]\n",
    "            q_per_token = torch.matmul(layer_embedding_norm, q_layer_head.T) + q_layer_bias_head\n",
    "            q_per_token_split_into_pairs = q_per_token.float().view(q_per_token.shape[0], -1, 2)\n",
    "            q_per_token_as_complex_numbers = torch.view_as_complex(q_per_token_split_into_pairs)\n",
    "            freqs_for_next_token = torch.outer(torch.tensor([seq_len]), freqs)\n",
    "            freqs_cis_next_token = torch.polar(torch.ones_like(freqs_for_next_token), freqs_for_next_token)\n",
    "            q_per_token_split_into_pairs_rotated = torch.view_as_real(q_per_token_as_complex_numbers * freqs_cis_next_token)\n",
    "            q_per_token_rotated = q_per_token_split_into_pairs_rotated.view(q_per_token.shape)\n",
    "\n",
    "            if head % GQA_num == 0:\n",
    "                v_per_token = torch.matmul(layer_embedding_norm, v_layer_head.T) + v_layer_bias_head\n",
    "                v_cache[layer][head//GQA_num] = torch.cat([v_cache[layer][head//GQA_num], v_per_token], dim=0)    # update v_cache\n",
    "\n",
    "                k_per_token = torch.matmul(layer_embedding_norm, k_layer_head.T) + k_layer_bias_head\n",
    "                k_per_token_split_into_pairs = k_per_token.float().view(k_per_token.shape[0], -1, 2)\n",
    "                k_per_token_as_complex_numbers = torch.view_as_complex(k_per_token_split_into_pairs)\n",
    "                k_per_token_split_into_pairs_rotated = torch.view_as_real(k_per_token_as_complex_numbers * freqs_cis_next_token)\n",
    "                k_per_token_rotated = k_per_token_split_into_pairs_rotated.view(k_per_token.shape)\n",
    "                k_cache[layer][head//GQA_num] = torch.cat([k_cache[layer][head//GQA_num], k_per_token_rotated], dim=0)    # update k_cache\n",
    "                \n",
    "            qk_per_token = torch.matmul(q_per_token_rotated, k_cache[layer][head//GQA_num].T)/(128)**0.5\n",
    "            #mask = torch.full((len(token_embeddings_unnormalized), len(token_embeddings_unnormalized)), float(\"-inf\"))   # don't need mask\n",
    "            #mask = torch.triu(mask, diagonal=1)\n",
    "            qk_per_token_after_masking = qk_per_token  # + mask\n",
    "            qk_per_token_after_masking_after_softmax = torch.nn.functional.softmax(qk_per_token_after_masking, dim=1).to(torch.bfloat16)\n",
    "            qkv_attention = torch.matmul(qk_per_token_after_masking_after_softmax, v_cache[layer][head//GQA_num])\n",
    "            qkv_attention_store.append(qkv_attention)\n",
    "\n",
    "        stacked_qkv_attention = torch.cat(qkv_attention_store, dim=-1)\n",
    "        w_layer = model[f\"model.layers.{layer}.self_attn.o_proj.weight\"]\n",
    "        embedding_delta = torch.matmul(stacked_qkv_attention, w_layer.T)\n",
    "        embedding_after_edit = final_embedding + embedding_delta\n",
    "        embedding_after_edit_normalized = rms_norm(embedding_after_edit, model[f\"model.layers.{layer}.post_attention_layernorm.weight\"])\n",
    "        w1 = model[f\"model.layers.{layer}.mlp.gate_proj.weight\"] \n",
    "        w2 = model[f\"model.layers.{layer}.mlp.down_proj.weight\"]\n",
    "        w3 = model[f\"model.layers.{layer}.mlp.up_proj.weight\"]\n",
    "        output_after_feedforward = torch.matmul(torch.functional.F.silu(torch.matmul(embedding_after_edit_normalized, w1.T)) * torch.matmul(embedding_after_edit_normalized, w3.T), w2.T)\n",
    "        final_embedding = embedding_after_edit+output_after_feedforward\n",
    "    \n",
    "    final_embedding = rms_norm(final_embedding, model[\"model.norm.weight\"])\n",
    "    #print(\"final_embedding.shape: \", final_embedding.shape)\n",
    "    logits = torch.matmul(final_embedding, model[\"lm_head.weight\"].T)\n",
    "    #print(\"logits.shape: \", logits.shape)\n",
    "    next_token = torch.argmax(logits, dim=-1)\n",
    "    #print(\"next_token: \", next_token)\n",
    "    print(\"token decode: \", tokenizer.decode([next_token.item()]))\n",
    "    seq_len += 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# thank you, i love you :)\n",
    "\n",
    "This is the end. Hopefully you enjoyed reading it!"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
